#include "c_string.h"
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <assert.h>

#define INNER_BUFF_SIZE     16

struct CStringStruct {
    union {
        char *ptr;
        char buff[INNER_BUFF_SIZE];
    } data;
    int size;
    int len;
};

static char* _DataPtr(CString *pthis)
{
    return pthis->size != 0 ? pthis->data.ptr : pthis->data.buff;
}

static const char* _DataPtrConst(const CString *pthis)
{
    return pthis->size != 0 ? pthis->data.ptr : pthis->data.buff;
}

static void _Reserve(CString *pthis, int size)
{
    if (size <= INNER_BUFF_SIZE)
        return;

    if (size >= pthis->size) {
        char* new_ptr = malloc(size);
        assert(new_ptr != NULL);

        memcpy(new_ptr, _DataPtr(pthis), pthis->len);
        if (pthis->size != 0)
            free(pthis->data.ptr);

        new_ptr[pthis->len] = 0;
        pthis->data.ptr = new_ptr;
        pthis->size = size;
    }
}

CString* CString_New(const char *text)
{
    CString *pthis = malloc(sizeof(CString));
    assert(pthis != NULL);

    pthis->len = strlen(text);
    if (pthis->len >= INNER_BUFF_SIZE) {
        pthis->size = pthis->len * 2 + 1;
        pthis->data.ptr = malloc(pthis->size);
        assert(pthis->data.ptr != NULL);
    } else {
        pthis->size = 0;
    }

    memcpy(_DataPtr(pthis), text, pthis->len);
    _DataPtr(pthis)[pthis->len] = 0;

    return pthis;
}

void CString_Delete(CString *pthis)
{
    assert(pthis != NULL);

    if (pthis->size != 0)
        free(pthis->data.ptr);

    free(pthis);
}

void CString_Reserve(CString *pthis, int size)
{
    assert(pthis != NULL);
    assert(size > 0);

    _Reserve(pthis, size);
}

void CString_Append(CString *pthis, const char *text)
{
    assert(pthis != NULL);
    assert(text != NULL);

    int text_len = strlen(text);
    int new_len = pthis->len + text_len;

    _Reserve(pthis, new_len * 2 + 1);
    memcpy(_DataPtr(pthis) + pthis->len, text, text_len);

    pthis->len = new_len;
    _DataPtr(pthis)[pthis->len] = 0;
}

char CString_GetAt(const CString *pthis, int index)
{
    assert(pthis != NULL);
    assert(index >= 0);
    return _DataPtrConst(pthis)[index];
}

void CString_SetAt(CString *pthis, int index, char ch)
{
    assert(pthis != NULL);
    assert(index >= 0);
    assert(index < pthis->size);
    _DataPtr(pthis)[index] = ch;
}

const char* CString_CStr(const CString *pthis)
{
    assert(pthis != NULL);
    return _DataPtrConst(pthis);
}

int CString_Length(const CString *pthis)
{
    assert(pthis != NULL);
    return pthis->len;
}

CString* CString_Concat(const CString *lhs, const CString *rhs)
{
    assert(lhs != NULL);
    assert(rhs != NULL);

    CString *pthis = malloc(sizeof(CString));
    assert(pthis != NULL);

    pthis->len = lhs->len + rhs->len;
    if (pthis->len >= INNER_BUFF_SIZE) {
        pthis->size = pthis->len * 2 + 1;
        pthis->data.ptr = malloc(pthis->size);
        assert(pthis->data.ptr != NULL);
    }

    memcpy(_DataPtr(pthis), _DataPtrConst(lhs), lhs->len);
    memcpy(_DataPtr(pthis) + lhs->len, _DataPtrConst(rhs), rhs->len);
    _DataPtr(pthis)[pthis->len] = 0;

    return pthis;
}

void CString_Erase(CString *pthis, int pos, int len)
{
    assert(pthis != NULL);
    assert(pos >= 0 && len > 0);
    assert((pos + len) < (pthis->size != 0 ? pthis->size : INNER_BUFF_SIZE));

    memmove(_DataPtr(pthis) + pos, _DataPtr(pthis) + pos + len, pthis->len - len - pos);
    pthis->len -= len;
    _DataPtr(pthis)[pthis->len] = 0;
}

